/*
 * Copyright Joachim Zobel
 *
 * You can consider TAL MIT style licensed. Do with it as you like,
 * but don't blame me for anything evil that TAL code does and
 * do not remove my name.
 *
 */


TAL = {

    parse: function TALparse(container, params) {
        return  TAL.process(container, params, container);
    },
    /*
 * function:	TALprocess
 * description: Fills a tal template using recursion
 * parameters:	oTmpl  - the template root node
 *				oData  - the data to fill in
 */
    process : function TALprocess(oTmpl, oData, oTmplOrig) {
        var	bRecurse	= true;
        var i;
        try {
            if (oTmpl.getAttributeNode) {
                var	sError	= null;

                if (oTmpl.tagName.toLowerCase() == TAL.addNsp("block"))
                {
                    sError		= TAL.block(oTmpl, oData);
                //	In TAL case, we do recurse
                }
                if (oTmpl.getAttributeNode(TAL.addNsp("define"))) {
                    sError		= TAL.define(oTmpl, oData);
                }
                if (oTmpl.getAttributeNode(TAL.addNsp("repeat"))) {
                    sError		= TAL.repeat(oTmpl, oData, oTmplOrig);
                    //	In TAL case, TALrepeat has already
                    //	taken care for recursion.
                    bRecurse	= false;
                }
                if (oTmpl.getAttributeNode(TAL.addNsp("condition"))) {
                    sError		= TAL.condition(oTmpl, oData);
                }
                if (oTmpl.getAttributeNode(TAL.addNsp("content"))) {
                    sError		= TAL.content(oTmpl, oData);
                    bRecurse	= false;
                }
                if (oTmpl.getAttributeNode(TAL.addNsp("replace"))) {
                    sError		= TAL.replace(oTmpl, oData);
                    bRecurse	= false;
                }
                if (oTmpl.getAttributeNode(TAL.addNsp("attributes"))) {
                    sError		= TAL.attributes(oTmpl, oData);
                //	In TAL case, we do recurse
                }
                if (oTmpl.getAttributeNode(TAL.addNsp("omit-tag"))) {
                    sError		= TAL.omitTag(oTmpl, oData);
                //	In TAL case, we do recurse
                }

                //	Error handling, throws an exception if no on-error is defined
                if ( sError )
                    handleError(sError, oTmpl, oTmplOrig);
            }
        }
        catch (e) {
            var oTmplAttributes="";
        
            if(/^tal:/i.test(oTmpl.tagName))//displays all of the attributes
                for (i=0; i< oTmpl.attributes.length; i++)
                    oTmplAttributes+=" "+oTmpl.attributes[i].name+"=\""+oTmpl.attributes[i].value+"\"";
            else
                for (i=0; i< oTmpl.attributes.length; i++)//displays namespaced attribute and id and class
                    if(/(^tal:|^id$|^class$)/i.test(oTmpl.attributes[i].name))
                        oTmplAttributes+=" "+oTmpl.attributes[i].name+"=\""+oTmpl.attributes[i].value+"\"";

            throw "Error while processing <"+ oTmpl.tagName.toLowerCase() + oTmplAttributes+">: "+e;
        }
        if (bRecurse) {
            //	recursion
            for (i=0; i<oTmpl.childNodes.length; i++) {
                TAL.process(oTmpl.childNodes[i], oData, oTmplOrig);
            }
        }
    },

    /***********************************************************************
     * TAL tag processors
     ***********************************************************************/

    /*
     * function:	TALcontent
     */

    content : function TALcontent(oTmpl, oData) {
        var sText	= TAL._entryByPath(oData,
            oTmpl.getAttribute(TAL.addNsp("content")));
        if (typeof sText == "undefined") {
            return "No data found for "+oTmpl.getAttribute(TAL.addNsp("content"))+".";
        }
        var oTextNode = document.createTextNode(sText);

        oTmpl.replaceChild(oTextNode, oTmpl.firstChild);
        oTmpl.removeAttributeNode(oTmpl.getAttributeNode(TAL.addNsp("content")));

        return null;
    },

    /*
     * function:	TALblock
     */
    block : function TALblock(oTmpl, oData) {
        //transform the tal:block in an element with an omit-tag
        //changes any non "tal" prefixed attribute with it
        for(var i=0;i<oTmpl.attributes.length;i++) {
            if(oTmpl.attributes[i].name.indexOf(TAL.getNsp())!=0) {
                oTmpl.setAttribute(TAL.addNsp(oTmpl.attributes[i].name),oTmpl.attributes[i].value);
            }
        }
        oTmpl.setAttribute(TAL.addNsp("omit-tag"),"");
        //omit tag handler will now do the work
        return null;
    },
    /*
     * function:	TALdefine
     */
    define : function TALdefine(oTmpl, oData) {
        var sRep	= oTmpl.getAttribute(TAL.addNsp("define"));
        var aNmX	= TAL._getNameExpr(sRep);

        oData[aNmX[0]]=TAL._entryByPath(oData, aNmX[1]);
        Logger.debug("Setted ",aNmX[0]," to ",oData[aNmX[0]]);

        return null;
    },

    /*
     * function:	TALreplace
     */

    replace: function TALreplace(oTmpl, oData) {
        var sText	= TAL._entryByPath(oData,
            oTmpl.getAttribute(TAL.addNsp("replace")));
        if (typeof sText == "undefined") {
            return "No data found for "+oTmpl.getAttribute(TAL.addNsp("replace"))+".";
        }
        $(oTmpl).replaceWith(sText);
        return null;
    },

    /*
     * function:	TALomit_tag
     */

    omitTag: function TALomit_tag(oTmpl, oData) {
        var sPath	= oTmpl.getAttribute(TAL.addNsp("omit-tag"));
        var	sCond	= TAL._entryByPath(oData, sPath);

        Logger.debug("sCond:", sCond);
        if (TAL._isTrue(sCond)||sCond==null) {
            $(oTmpl).replaceWith(oTmpl.innerHTML);
        }

        return null;
    },

    /*
     * function:	TALattributes
     */

    attributes : function TALattributes(oTmpl, oData) {
        var sAttrs	= oTmpl.getAttribute(TAL.addNsp("attributes"));
        var	aAttrs	= sAttrs.split(/;/);
	
        var sAttr	= null;
        for (;;sAttr = aAttrs.shift()) {
            var aNmX	= TAL._getNameExpr(sAttr);
            var oVal	= TAL._entryByPath(oData, aNmX[1]);
            if (typeof oVal == "undefined"
                ||	oVal == null) {
                //	remove attribute
                oTmpl.removeAttributeNode(oTmpl.getAttributeNode(aNmX[0]));
            }
            else {
                oTmpl.setAttribute(aNmX[0], oVal);
            }
        }

        oTmpl.removeAttributeNode(oTmpl.getAttributeNode(TAL.addNsp("attributes")));

        return null;
    },

    /*
     * function:	TALrepeat
     */

    repeat: function TALrepeat(oTmpl, oData) {
        var sRep	= oTmpl.getAttribute(TAL.addNsp("repeat"));
        var aNmX	= TAL._getNameExpr(sRep);

        var	aDatas	= TAL._entryByPath(oData, aNmX[1]);
        var	oParent	= oTmpl.parentNode;
        //	The repeat attribute is no longer needed
        oTmpl.removeAttributeNode(oTmpl.getAttributeNode(TAL.addNsp("repeat")));

        //creating HTML for recreating the element
        var attributesHTML=[];
        for(var j=0;j<oTmpl.attributes.length;j++)
            if(typeof oTmpl.attributes[i] != "undefined")
                attributesHTML[j]=[oTmpl.attributes[i].name, '= "', oTmpl.attributes[i].value,'"'].join('');

        var startHTML=['<',oTmpl.tagName.toLowerCase()," ",attributesHTML.join(" "),'>'].join('');
        var endHTML=['</',oTmpl.tagName.toLowerCase(),'>'].join('');

        var iterationsResults = [];
        var i=0;
        if (typeof aDatas.length == "undefined") { //hashmap
            
            var item=null;
            var lastItem=null;
            var hashSize=0;
            //get the size and the last element of a hash
            for (item in aNmX[1]) {
                lastItem=item;
                hashSize++;
            }
            
            for (item in aNmX[1]) {
                i++;
                var	oLocData		= {};
                //aNmX[0] is current iterator name
                //aDatas is the collection in which we iterate
                oLocData[aNmX[0]]	= aDatas[item];
                oLocData.repeat     = oLocData.repeat||{};//if repeat exists, don't remove it
                oLocData.repeat[aNmX[0]]={
                    key: item,
                    number: i,
                    even: i%2==0,
                    odd: i%2!=0,
                    start: i==0,
                    end: item==lastItem,
                    length: hashSize
                }
                var daddy   = document.createElement("div");
                var oNew	= document.createElement("div");
                
                oNew.innerHTML=[startHTML,oTmpl.innerHTML,endHTML].join("\n");
                daddy.appendChild(oNew);
                TAL.parse(daddy, oLocData);
                iterationsResults[i]=oNew.innerHTML;
            }
        } else { //array

            for (i=0; i<aDatas.length; i++) {
                var	oLocData		= {};
                //aNmX[0] is current iterator name
                //aDatas is the collection in which we iterate
                oLocData[aNmX[0]]	= aDatas[i];
                oLocData.repeat     = oLocData.repeat||{};//if repeat exists, don't remove it
                oLocData.repeat[aNmX[0]]={
                    key: "",
                    number: i,
                    even: i%2==0,
                    odd: i%2!=0,
                    start: i==0,
                    end: i==aDatas.length,
                    length: aDatas.length
                }
                var daddy   = document.createElement("div");
                var oNew	= document.createElement("div");

                oNew.innerHTML=[startHTML,oTmpl.innerHTML,endHTML].join("\n");
                daddy.appendChild(oNew);
                TAL.parse(daddy, oLocData);
                iterationsResults[i]=oNew.innerHTML;
            }
        }
        $(oTmpl).replaceWith(iterationsResults.join("\n"));
        return null;
    },

    /*
     * function:	TALcondition
     */

    condition: function TALcondition(oTmpl, oData) {
        var sPath	= oTmpl.getAttribute(TAL.addNsp("condition"));
        var	sCond	= TAL._entryByPath(oData, sPath)
	
        //	remove tal attribute
        oTmpl.removeAttributeNode(oTmpl.getAttributeNode(TAL.addNsp("condition")));

        if (!TAL._isTrue(sCond)) {
            $(oTmpl).replaceWith("");
        }

        return null;
    },

    /***********************************************************************
     * Helpers
     ***********************************************************************/

    /**
     * function:	entryByPath
     * parameters:	oObj   - an nested Object
     *				sPath  - a list of property names seperated by /
     *				oCreate - (opt.) Create everything along the path
     *						  and put TAL at the end
     * return:      the property obtained by following the path
     */

    _entryByPath: function (oObj, sPath, oCreate) {
        if (sPath == null )
            return null;
        if (/^string:/i.test(sPath)) {
            Logger.debug("found a string:",sPath.substr(7));
            return sPath.substr(7);
        }
        if (/^boolean:/i.test(sPath)) {
            Logger.debug("found a boolean:",sPath.substr(8));
            return {
                "true":true,
                "false":false
            }[sPath.substr(8)];
        }
        if (/^not:/i.test(sPath)) {
            return !TAL._entryByPath(oObj, sPath.substr(4));
        }
        if (/^exists:/i.test(sPath)) {
            var check = TAL._entryByPath(oObj, sPath.substr(7));
            return typeof check != "undefined" && check != null;
        }
        var	aPath	= sPath.split("/");
        var	oRtn	= oObj;
        for (var i=0; i < aPath.length; i++) {
            if (	typeof oRtn[aPath[i]] == "undefined"
                //	oCreate overwrites
                ||	oCreate && (i==aPath.length-1)) {
                if (oCreate) {
                    if (i==aPath.length-1) {
                        //	Put oCreate at the end
                        oRtn[aPath[i]]	= oCreate;
                    }
                    else {
                        //	Just another node along the path
                        oRtn[aPath[i]]	= new Object();
                    }
                }
                else
                    return null;
            
            }

            oRtn	= oRtn[aPath[i]];
        }
        return oRtn;
    },

    /*
     * function:	handleError
     * description: Simply does an alert at the moment
     * parameters:	sError  - the template root node
     * return: true - fatal error
     */
    handleError : function (sError, oTmpl, oTmplOrig) {
        //TODO : search for on-error attribute while getting up in the hierarchy
        throw sError;
    },

    /*
     * function:	getNameExpr
     * description: splits a string at the first blank
     * parameters:	sNX  - will be split
     * return:		the two parts as an array
     */

    _getNameExpr : function (sNX) {
        var aNX		= sNX.split(/ /);
        var sFirst	= "";
        //	ltrim
        while (sFirst == "") {
            sFirst	= aNX.shift();
        }
        return [sFirst, aNX.join(" ")];
    },

    /*
     * function:	getNsp
     * description: get the namespace
     * parameters:	sTag  - the tag
     * return: the tag prefixed with the namespace
     */
    getNsp : function () {
        return "tal";
    },

    /*
     * function:	addNsp
     * description: adds the namespace to a tag
     * parameters:	sTag  - the tag
     * return: the tag prefixed with the namespace
     */
    addNsp : function (sTag) {
        return TAL.getNsp()+":"+sTag;
    },

    /*
     * function:	isTrue
     */
    _isTrue : function (sCond) {
        return sCond && (sCond != "")
    },

    /*
     * function:	TALextract
     * description: Extracts the data structure from the document.
     * parameters:	oTmpl - the (current) template node
     *				oData - the Data
     */

    extract : function TALextract(oTmpl, oData)
    {
        var i;
        for (i=0; i<oTmpl.attributes.length; i++) {
            var	oAttr	= oTmpl.attributes[i];
            var aSTag	= oAttr.nodeName.split(":");
            //	Check if it is a TAL attribute
            if (aSTag.length>=2 && aSTag[0] == TAL.getNsp()) {
                switch(aSTag[1]) {
                    case "content":
                    {
                        TAL._entryByPath(oData, oAttr.nodeValue);
                        break;
                    }
                }
            }
        }
	
        for (i=0; i<oTmpl.childNodes.length; i++) {
            TAL.extract(oTmpl.childNodes[i], oData);
        }
    }
}
